package cn.kzgg.ymcc.service.impl;

import cn.kzgg.ymcc.Exception.GlobleException;
import cn.kzgg.ymcc.domain.Course;
import cn.kzgg.ymcc.domain.CourseMarket;
import cn.kzgg.ymcc.domain.CourseOrder;
import cn.kzgg.ymcc.domain.CourseOrderItem;
import cn.kzgg.ymcc.dto.CourseCreateOrderFormDto;
import cn.kzgg.ymcc.feign.CourseUserLearnFeignClient;
import cn.kzgg.ymcc.mapper.CourseOrderMapper;
import cn.kzgg.ymcc.result.JSONResult;
import cn.kzgg.ymcc.service.ICourseOrderItemService;
import cn.kzgg.ymcc.service.ICourseOrderService;
import cn.kzgg.ymcc.util.AssertUtil;
import cn.kzgg.ymcc.util.CodeGenerateUtils;
import cn.kzgg.ymcc.vo.CourseItemVo;
import cn.kzgg.ymcc.vo.CourseOrderVo;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import dto.PayDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrBuilder;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author kzgg
 * @since 2022-07-29
 */
@Service
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CourseUserLearnFeignClient courseUserLearnFeignClient;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 提交订单
     * <p>
     * 1.参数校验
     * 2.token是否有效，如果有效才下单
     * 3.业务校验  课程必须是上架的，课程Id不能重复===
     * 4.保存订单 （主订单-子订单）
     * 4.1 保存主订单  主订单中的总金额是没有
     * 4.2 保存子订单 统计出总金额
     * 4.3 修改主订单金额
     * =======
     * 5.删除防重复token
     * 我们下单成功，需要及时把redis中的token删除掉，竟可能保证不重复下单
     *
     * @param dto
     */
    @Override
    @Transactional
    public String placeOrder(CourseCreateOrderFormDto dto) {
        //1.参数校验JSR303
        //2.token是否有效，如果有效才下单,token是courseIds字符串+登录人Id
        String strIds = StringUtils.join(dto.getCourseIds(), ",");
        //写死登陆人
        Long loginId = 1L;
        String key = strIds + ":" + loginId;
        //从redis获取token
        Object tmpToken = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tmpToken, "token失效，请重新下单");
        AssertUtil.isEquals(dto.getToken(), tmpToken.toString(), "token有误请重新下单");

        //3.业务校验  课程必须是上架的，课程Id不能重复===需要链接课程微服务
        JSONResult jsonResult = courseUserLearnFeignClient.CourseOrderInfo(strIds);
        AssertUtil.isTrue(jsonResult.isSuccess(), "信息有误，下单失败");
        AssertUtil.isNotNull(jsonResult.getData(), "服务不可达，下单失败");
        //将拿到的数据转为主订单CourseOrderVo对象对应的courseInfos，就是一个一个子订单CourseItemVo信息
        String dataStr = JSON.toJSONString(jsonResult.getData());
        CourseOrderVo courseOrderVo = JSON.parseObject(dataStr, CourseOrderVo.class);
        List<CourseItemVo> courseInfos = courseOrderVo.getCourseInfos();
        AssertUtil.isNotNull(courseInfos, "课程信息有误请重新下单");

        //4.保存订单 （主订单-子订单）
        Date date = new Date();
        //保存主订单
        CourseOrder courseOrder = new CourseOrder();
        courseOrder.setCreateTime(date);
        courseOrder.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        courseOrder.setPayType(dto.getPayType());
        courseOrder.setStatusOrder(0);
        courseOrder.setUserId(loginId);
        courseOrder.setVersion(0);
        //insert(courseOrder);

        //保存子订单：包括金额,要用到子订单CourseItemVo
        StrBuilder sb = new StrBuilder();
        sb.append("课程[");
        courseInfos.forEach(courseItemVo -> {
            Course course = courseItemVo.getCourse();
            CourseMarket courseMarket = courseItemVo.getCourseMarket();
            CourseOrderItem courseOrderItem = new CourseOrderItem();
            //courseOrderItem.setCourseId(course.getId());
            courseOrderItem.setAmount(courseMarket.getPrice());
            courseOrderItem.setCount(1);
            courseOrderItem.setCreateTime(date);
            courseOrderItem.setCourseId(course.getId());
            courseOrderItem.setCourseName(course.getName());
            courseOrderItem.setCoursePic(course.getPic());
            courseOrderItem.setOrderNo(courseOrder.getOrderNo());
            courseOrderItem.setVersion(courseOrder.getVersion());
            courseOrder.setTotalAmount(courseOrder.getTotalAmount().add(courseOrderItem.getAmount()));
            courseOrder.setTotalCount(courseOrder.getTotalCount() + 1);
            // courseOrderItemService.insert(courseOrderItem);
            courseOrder.getItems().add(courseOrderItem);//将一个一个的courseOrderItem装进courseOrder
            sb.append(course.getName()).append(",");
        });
        sb.append("]");
        //修改主订单金额
        courseOrder.setTitle(sb.toString());
        //updateById(courseOrder);

        //发布s事务消息=============================保存本地事务和发布消息要有原子性，因此不能主动保存订单而因由监听器另建保存订单

        HashMap<Object, Object> extParams = new HashMap<>();
        extParams.put("userId", strIds);
        extParams.put("courseId", loginId);
        PayDto payDto = new PayDto(
                courseOrder.getTotalAmount(),
                courseOrder.getPayType(),
                null,
                courseOrder.getOrderNo(),
                courseOrder.getUserId(),
                JSON.toJSONString(extParams),
                courseOrder.getTitle()

        );
        log.info("发送事务消息");


        //构建消息体  MessageBuilder工具类
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-order-producer",//和监听器上注解申明的名字一致
                "course-order-topic:course-order-tag",
                message,//消息体
                courseOrder//额外参数
        );
        log.info("发送完事务消息--检查事务消息状态");
        LocalTransactionState state = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if (state != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK) {
            log.info("校验发送事务消息状态异常");
            throw new GlobleException("下单失败");
        }

//发布支付超时延迟消息==============================
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "paytimeout-topic:paytimeout-tag",//topic:tag
                    MessageBuilder.withPayload(courseOrder.getOrderNo()).build(),//消息体
                    2000,//发送延迟消息的超时时间
                    4//延迟等级  如果是30分钟 16
            );
            SendStatus sendStatus1 = sendResult.getSendStatus();
            if (sendStatus1 != SendStatus.SEND_OK) {
                 /*
                   先尝试重试3次，如果3次重试都发不出去
                     1.记录一个log表
                     2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }
//发布延迟消息结束===============================

        //5.删除防重复token
        redisTemplate.delete(key);
        return courseOrder.getOrderNo();
    }

    /**
     * 执行本地事务
     *
     * @param courseOrder
     */
    @Override
    public void saveCourseOrderAndItems(CourseOrder courseOrder) {
        //根据订单号判断是否已经下过单
        CourseOrder orderSn = selectByOrderSn(courseOrder.getOrderNo());
        AssertUtil.isNull(orderSn, "订单已存在");
        //保存订单
        //主订单
        insert(courseOrder);
        //子订单
        courseOrder.getItems().forEach(item -> {
            item.setOrderId(courseOrder.getId());
            courseOrderItemService.insert(item);
        });
    }

    /**
     * 检查本地事务，根据订单号查询订单
     *
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrder selectByOrderSn(String orderNo) {
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        return selectOne(wrapper);
    }

    @Override
    public void payOrder(CourseOrder courseOrder) {
        courseOrder.setStatusOrder(CourseOrder.STATE_PAY_SUCCESS);
        courseOrder.setUpdateTime(new Date());
        updateById(courseOrder);
    }

    /**
     * 支付超时取消
     *
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        //应该拿着订单号去支付宝查询一波，看订单是否支付成功  不调用了
        CourseOrder courseOrder = selectByOrderSn(orderNo);
        AssertUtil.isNotNull(courseOrder, "订单不存在");
        if (courseOrder.getStatusOrder().intValue() != CourseOrder.STATE_PAY_SUCCESS.intValue()) {
            courseOrder.setStatusOrder(CourseOrder.STATE_CANCEL);
            updateById(courseOrder);
        }
    }
}
